#include <linux/pci.h>
#include <linux/platform_device.h> 
#include <linux/irq.h>  
#include <linux/log2.h> 
#include <linux/module.h>
#include <linux/moduleparam.h> 
#include <linux/slab.h> 
#include <linux/dmi.h> 
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <acpi/acpi.h>
#include <linux/dbg_tool.h>
#include "pm_dbg.h"
#include "pm_entry.h"

struct ts_dev_list {
    struct device dev;
    struct klist list;
    struct delayed_work work_qd;
};

static struct klist *ts_p_head;
struct bus_type ts_dev_bus; 

/* it find a device form pmlist
 */
struct pm_list_find {
	char * dev_name;
	struct device *find_dev;
};

static void pm_list_callback(struct device *dev, void *point)
{
	struct pm_list_find * find_dev;
	find_dev = point;
	if(strstr(dev_name(dev) ,find_dev->dev_name))
	{
		if(!find_dev->dev_name)
			find_dev->dev_name = dev;
	}
}

struct device * find_dev_from_pmlist(char *name)
{
	struct device *dev;
	struct list_head * m_dpm_list;

	m_dpm_list = get_dpm_list();
	list_for_each_entry(dev, m_dpm_list, power.entry) {
		if(dev)
		  printk("zoucao dev:%s\n", dev_name(dev));
	}
	//list_add_tail(&dev->power.entry, &dpm_list);
	return	NULL;
}

static int ts_dev_runtime_suspend(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

static int ts_dev_runtime_resume(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    pm_runtime_mark_last_busy(dev);
    return 0;
}

static int ts_dev_runtime_idle(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

static void ts_dev_release_dev(struct device *dev)
{
    printk("zoucao %s\n", __func__);
}

static int ts_dev_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_prepare(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

void ts_dev_complete(struct device *dev)
{
    printk("zoucao %s\n", __func__);
}

int ts_dev_suspend(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_resume(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_freeze(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_thaw(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_poweroff(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

int ts_dev_restore(struct device *dev)
{
    printk("zoucao %s\n", __func__);
    return 0;
}

static const struct dev_pm_ops ts_dev_device_pm_ops = {
    .prepare =  ts_dev_prepare,
    .complete =	ts_dev_complete,
    .suspend =  ts_dev_suspend,
    .resume =	ts_dev_resume,
    .freeze =   ts_dev_freeze,
    .thaw =	ts_dev_thaw,
    .poweroff = ts_dev_poweroff,
    .restore =	ts_dev_restore,
#ifdef CONFIG_PM_RUNTIME
    .runtime_suspend =  ts_dev_runtime_suspend,
    .runtime_resume =	ts_dev_runtime_resume,
    .runtime_idle =	ts_dev_runtime_idle,
#endif
};

struct device_type ts_dev_device_type = {
    .name    = "pm_dbg_dev_type",
    .release = ts_dev_release_dev,
    .uevent  = ts_dev_dev_uevent,
  //.devnode =	pm_dbg_devnode,
#ifdef CONFIG_PM
    .pm      = &ts_dev_device_pm_ops,
#endif
};
struct ts_struct_arr{
    int size ;
    //struct ts_dev_list *dev_arr[10];
    struct device *dev_arr[10];
    struct delayed_work work_qd;
};

struct ts_struct_arr ts_dev_arr;
unsigned long ts_pm_runtime_autosuspend_expiration(struct device *dev)
{
	int autosuspend_delay;
	long elapsed;
	unsigned long last_busy;
	unsigned long expires = 0;
    printk("zoucao %ld\n", elapsed);

	if (!dev->power.use_autosuspend)
		goto out;

	autosuspend_delay = ACCESS_ONCE(dev->power.autosuspend_delay);
	if (autosuspend_delay < 0)
		goto out;

	last_busy = ACCESS_ONCE(dev->power.last_busy);
	elapsed = jiffies - last_busy;
    printk("zoucao %ld\n", elapsed);
	if (elapsed < 0)
		goto out;	/* jiffies has wrapped around. */

	/*
	 * If the autosuspend_delay is >= 1 second, align the timer by rounding
	 * up to the nearest second.
	 */
	expires = last_busy + msecs_to_jiffies(autosuspend_delay);
	if (autosuspend_delay >= 1000)
		expires = round_jiffies(expires);
	expires += !expires;
	if (elapsed >= expires - last_busy)
		expires = 0;	/* Already expired. */

 out:
	return expires;
}
static void workqueue_func(struct work_struct *work)
{
    struct device *dev;
    int ret;
    dev = ts_dev_arr.dev_arr[0];
    printk("zoucao %s expect:%ld \n", __func__ ,ts_pm_runtime_autosuspend_expiration(dev));
    print_dev_runtime_info(dev);
    ret = __pm_runtime_resume(dev ,RPM_GET_PUT);
    pm_runtime_put_sync_autosuspend(dev);
    //ret = __pm_runtime_suspend(dev ,RPM_GET_PUT | RPM_AUTO);

    schedule_delayed_work(&ts_dev_arr.work_qd ,100);
}
/* add one stande device, it has the runtime and suspend/resume interface
 *
 */
struct device *ts_dev_add(char *name)
{
    struct device *dev;
    struct ts_dev_list *dev_list;
    int ret;

    dev_list = kzalloc(sizeof(struct ts_dev_list), GFP_KERNEL); 
    if(!dev_list)
        goto ERR;
    
    dev = &dev_list->dev;
    device_initialize(dev);
    dev->init_name = name;
    dev->bus = &ts_dev_bus;
    dev->type = &ts_dev_device_type;
    dev->groups = NULL;
    ret = device_add(dev);
    if (ret)
    {
	    printk("zoucao add %s device failed ret:%d\n" ,name,  ret);
	    return NULL;
    }

    pm_runtime_enable(dev);
    pm_runtime_allow(dev);
    pm_runtime_use_autosuspend(dev);
    pm_runtime_set_autosuspend_delay(dev,2000);
    //ret = __pm_runtime_resume(dev ,RPM_GET_PUT);
    //pm_runtime_set_autosuspend_delay(dev,11);
    //ret = pm_runtime_suspend(dev);
    //ret = __pm_runtime_suspend(dev ,RPM_ASYNC);
    //printk("zoucao add %s device failed ret:%d\n", __func__,  ret);
    
    //print_dev_runtime_info(dev);
    //ret = pm_runtime_resume(dev);
    //__pm_runtime_use_autosuspend(dev, 10);
    //pm_runtime_allow(dev);
    //cnt = ts_dev_arr.size;
    //ts_dev_arr.dev_arr[cnt + 1] =  dev_list;
    //ts_dev_arr.size = cnt + 1;
    return dev;
ERR:
    return NULL;
}

void ts_dev_del(struct device *dev)
{
    if(!dev)
        return;
    device_del(dev);
    put_device(dev);
}

/* drv part
 *
 */
int ts_bus_match(struct device *dev, struct device_driver *drv)
{
    return 0;
}

int ts_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    return 0;
}

int ts_bus_probe(struct device *dev)
{
    return 0;
}

int ts_bus_remove(struct device *dev)
{
    return 0;
}

int ts_bus_suspend(struct device *dev, pm_message_t state)
{
    return 0;
}
 
int ts_bus_resume(struct device *dev)
{
    return 0;
}

struct bus_type ts_dev_bus = {
   .name  = "ts_dev_bus",
   .match = ts_bus_match,
   .probe = ts_bus_probe,
   .uevent = ts_bus_uevent,
   .suspend = ts_bus_suspend,
   .resume  = ts_bus_resume,
};

//special the device count
#define DEV_CNT 2
int ts_dev_bus_init(void)
{
    int ret ,i;
    ret = bus_register(&ts_dev_bus);
    if(ret)
        return -ENODEV;

    ts_p_head = kzalloc(sizeof(struct ts_dev_list) ,GFP_KERNEL);
    if (!ts_p_head)
        goto ERR;

    ts_dev_arr.size = DEV_CNT;
    for( i = 0; i< ts_dev_arr.size; i++)
    {
        char dev_name[24] = {0};
        struct device *dev;

        sprintf(dev_name,"test_dev%d", i);
        dev = ts_dev_add(dev_name);
        if(!dev)
            return -ENODEV;
        ts_dev_arr.dev_arr[i] = dev;
    }
    INIT_DELAYED_WORK(&ts_dev_arr.work_qd ,workqueue_func);
    schedule_delayed_work(&ts_dev_arr.work_qd, 100);

    return 0;
ERR:
    bus_unregister(&ts_dev_bus);
    return -ENODEV;
}

static int pm_bus_rescan_devices(struct device *dev,void *data)
{
	printk("bus:%s ", dev->bus->name);
    if(dev->bus && dev->bus->dev_name)
	    printk("bus_dev:%s ", dev->bus->dev_name);
    if(dev_name(dev))
	    printk("%s ", dev_name(dev));

    return 0;
}

int ts_dev_bus_exit(void)
{
    int i;
    bus_for_each_dev(&ts_dev_bus ,NULL, NULL, pm_bus_rescan_devices);
    flush_delayed_work(&ts_dev_arr.work_qd);
    cancel_delayed_work_sync(&ts_dev_arr.work_qd);
    for( i = 0; i< ts_dev_arr.size; i++)
    {
        ts_dev_del(ts_dev_arr.dev_arr[i]);
        ts_dev_arr.dev_arr[i] = NULL;
    }
    ts_dev_arr.size = 0;
    bus_unregister(&ts_dev_bus);
    kfree(ts_p_head);
    return 0;
}

MODULE_LICENSE("GPL");
